#include "defines.h"
#include "serial.h"
#include "lib.h"
#include "xmodem.h"
#include "elf.h"
#include "interrupt.h"
#include "dram.h"
#include "timer.h"
#include "nic.h"
#include "net.h"

#define BUFSIZE 32

static int getline(unsigned char *buf)
{
    int i = 0, c;

    while (1) {
        c = serial_recv_byte(SERIAL_DEFAULT_DEVICE);

        if (c == -1) return -1;
        if (c == '\t') continue; // バックスペースでずれるので無視

        if (c == '\r') {
            putc('\n');
            buf[i++] = '\0';
            break;
        }

        if (c == '\b') {
            if (i > 0) {
                puts("\b \b");
                i--;
            }
        } else if (i < BUFSIZE - 1) {
            putc((unsigned char)c);
            buf[i++] = c;
        }
    }

    return i-1;
}

static void print_IP(uint8 *buf)
{
    putval(buf[0], 0);
    puts(".");
    putval(buf[1], 0);
    puts(".");
    putval(buf[2], 0);
    puts(".");
    putval(buf[3], 0);
}

static void print_MAC(uint8 *buf)
{
    int i;
    for (i = 0; i < 6; i++) {
        putxval(buf[i], 2);
        if (i != 5) puts(":");
    }
}

static char *getarg(char **pp)
{
    char *p = *pp;
    char *q;

    while (*p == ' ') p++;
    if (*p == '\0') return NULL;
    q = p;
    while (*p != ' ' && *p != '\0') p++;
    if (*p == ' ') *p++ = '\0';

    *pp = p;
    return q;
}

#define P1DDR   ((volatile uint8 *)0xfee000)
#define P2DDR   ((volatile uint8 *)0xfee001)
#define P6DDR   ((volatile uint8 *)0xfee005)
#define P8DDR   ((volatile uint8 *)0xfee007)

static void port_init()
{
    *P1DDR = 0xff;
    *P2DDR = 0x07;
    *P8DDR = 0xec;
}

static void reset() // 内部リセット。WDT使用
{
    INTR_DISABLE;
    *((volatile uint16 *)0xffff8c) = 0xa560;
}

static void wrst_clear()    // 内部リセットフラグクリア
{
    if (*(uint8 *)0xffff8f && 0x80) { // 読まないとクリアしないことがある
        *((volatile uint16 *)0xffff8e) = 0xa500;
    }
}

static int init(void)
{
    extern char erodata, data_start, edata, bss_start, ebss;

    wrst_clear();

    memcpy(&data_start, &erodata, (long)&edata - (long)&data_start);
    memset(&bss_start, 0, (long)&ebss - (long)&bss_start);

    softvec_init();

    serial_init(SERIAL_DEFAULT_DEVICE);

    port_init();

    dram_init();

    nic_init();

    return 0;
}

static int dump(char * buf, unsigned long size)
{
    unsigned long i;

    for (i = 0; i < size; i++) {
        putxval(buf[i], 2);
        if ((i & 0xf) == 0xf) {
            puts("\n");
        } else {
            if ((i & 0xf) == 7)
                puts(" ");
            puts(" ");
        }
    }
    puts("\n");

    return 0;
}

static const char *COMMANDS[] = {"boot", "dump", "help", "load", "reset", "run", "set", "tftp", NULL};

#define FILENAMESIZE 16
#define DEFAULT_NAK_INTERVAL 5
static const uint8 default_src_IP[] = {192, 168, 0, 10};
static const uint8 default_dst_IP[] = {192, 168, 0, 2};
#define DEFAULT_FILENAME "kozos"
#define DEFAULT_TFTP_PORT 10000

int main(void)
{
    static char buf[BUFSIZE];
    static long size;
    static unsigned char *loadbuf = NULL;
    extern char buffer_start, bootbuf_start;
    char *entry_point;
    void (*f)(void);

    static net_params params;
    static char filename[FILENAMESIZE + 1];
    extern char buffer_end, netbuf_start;
    static int nak_interval;
    net_params *np = &params;
    uint8 *nbuf = (uint8 *)&netbuf_start;

    INTR_DISABLE;

    init();

    puts("kzload (KOZOS boot loader) started.\n");

    nic_MAC(np->src_MAC);
    puts("MAC "); print_MAC(np->src_MAC); puts("\n");

    nak_interval = DEFAULT_NAK_INTERVAL;
    memcpy(np->src_IP, default_src_IP, 4);
    memcpy(np->dst_IP, default_dst_IP, 4);
    strcpy(filename, DEFAULT_FILENAME);
    np->src_port = DEFAULT_TFTP_PORT;

    while (1) {
        int i;
        char *p, *q;

        puts("kzload> ");

        i = getline(buf);

        if (i == -1) {
            puts("serial error !!\n");
            serial_error_clear(SERIAL_DEFAULT_DEVICE);
            continue;
        }

        p = buf;
        q = getarg(&p);
        if (!q) continue; // 空行はパス

        if (!strcmp(q, "load")) {
            xmodem_set_nak_interval(nak_interval);
            loadbuf = &buffer_start;
            size = xmodem_recv(loadbuf);

            delay_s(1); /* もしかするとputsがXMODEMに行ってしまうから? */

            if (size < 0) {
                loadbuf = NULL;
                puts("XMODEM receive failed !!\n");
                continue;
            }
            puts("XMODEM receive succeeded.\n");

            q = getarg(&p);
            if (q && !strcmp(q, "-r")) {
                entry_point = elf_load(loadbuf);
                if (entry_point) {
                    puts("starting from enrty point: ");
                    putxval((unsigned long)entry_point, 0);
                    puts("\n");
                    f = (void (*)(void))entry_point;
                    f();
                } else {
                    puts("Run Error !!\n");
                }
            }

        } else if (!strcmp(q, "dump")) {
            if (loadbuf) {
                puts("size: "); putval(size, 0); puts("\n");
                dump(loadbuf, size);
            } else {
                puts("no data.\n");
            }

        } else if (!strcmp(q, "run")) {
            if (!loadbuf)
                puts("no data.\n");

            entry_point = elf_load(loadbuf);
            if (entry_point) {
                puts("starting from enrty point: ");
                putxval((unsigned long)entry_point, 0);
                puts("\n");
                f = (void (*)(void))entry_point;
                f();
            } else {
                puts("Run Error !!\n");
            }

        } else if (!strcmp(q, "boot")) { /* 転送しながら直接ロード */
            xmodem_set_nak_interval(nak_interval);
            entry_point = elf_boot(&bootbuf_start, &bootbuf_start + XMODEM_BLOCK_SIZE);

            delay_s(1);

            if (entry_point) {
                puts("starting from enrty point: ");
                putxval((unsigned long)entry_point, 0);
                puts("\n");
                f = (void (*)(void))entry_point;
                f();
            } else {
                puts("Boot failed !!\n");
            }

        } else if (!strcmp(q, "help")) {
            const char **p;
            for (p = COMMANDS; *p; p++) {
                puts(*p);
                puts("\n");
            }

        } else if (!strcmp(q, "tftp")) {
            int run = 0;

            q = getarg(&p);

            if (q && !strcmp(q, "-r")) {
                run = 1;
                q = getarg(&p);
            }

            if (q) {
                if (strlen(q) <= FILENAMESIZE) {
                    strcpy(filename, q);
                } else {
                    puts("File name is too long.\n");
                    continue;
                }
            }

            if (mac_get(nbuf, np) < 0) {
                puts("ARP timeout.\n");
                continue;
            }

            if (tftp_down(nbuf, np, filename, (uint8 *)&buffer_start,
                          (uint32)&buffer_end - (uint32)&buffer_start, &size)
                    < 0)
            {
                // エラーメッセージはtftp_downで表示
                loadbuf = NULL;
                continue;
            }

            puts("Transfer completed (" );
            puts(filename);
            puts(", ");
            putval(size, 0);
            puts(" bytes)\n");

            loadbuf = &buffer_start;

            if (run) {
                entry_point = elf_load(loadbuf);
                if (entry_point) {
                    puts("starting from enrty point: ");
                    putxval((unsigned long)entry_point, 0);
                    puts("\n");
                    f = (void (*)(void))entry_point;
                    f();
                } else {
                    puts("Run Error !!\n");
                }
            }

        } else if (!strcmp(q, "set")) {
            char *q1, *q2;

            q1 = getarg(&p);
            q2 = getarg(&p);

            if (q2) {
                if (!strcmp(q1, "filename") || !strcmp(q1, "fn")) {
                    if (strlen(q2) <= FILENAMESIZE) {
                        strcpy(filename, q2);
                    } else {
                        puts("File name is too long.\n");
                    }

                } else if (!strcmp(q1, "dst_ip") || !strcmp(q1, "dip")) {
                    if (parse_IP(np->dst_IP, q2) < 0)
                        puts("input IP error.\n");

                } else if (!strcmp(q1, "src_ip") || !strcmp(q1, "sip")) {
                    if (parse_IP(np->src_IP, q2) < 0)
                        puts("input IP error.\n");

                } else if (!strcmp(q1, "nak_interval") || !strcmp(q1, "ni")) {
                    int i = atoi(q2);
                    if (1 <= i && i <= 26) {
                        nak_interval = i;
                    } else {
                        puts("input value error.\n");
                    }

                } else if (!strcmp(q1, "tftp_port") || !strcmp(q1, "tp")) {

                    np->src_port = atoi(q2); // オーバーフローしても大丈夫なようだ

                } else {
                    puts("unknown parameter.\n");
                }

            } else {
                puts("nak_interval(ni): ");
                putval(nak_interval, 0);
                puts("\n");

                puts("filename(fn): ");
                puts(filename);
                puts("\n");

                puts("src_ip(sip): ");
                print_IP(np->src_IP);
                puts("\n");

                puts("dst_ip(dip): ");
                print_IP(np->dst_IP);
                puts("\n");

                puts("tftp_port(tp): ");
                putval(np->src_port, 0);
                puts("\n");
            }

        } else if (!strcmp(q, "reset")) {

            reset();

        } else {
            puts("unknown command.\n");
        }
    }

    return 0;
}
